package push2java;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * The Compiler will produce a PushProgram that can be used to evaluate the
 * compiled commands using some number of arguments.
 *
 * @author James Pettit
 */
public class PushProgram {

    public float fitness;
    public List<Expression> expressions;
    private State state;
    private static List<Method> instructions;

    public PushProgram(List<Expression> expressions) {
        this.expressions = new LinkedList<Expression>();
        for (Expression e : expressions) {
            this.expressions.add(e.copy());
        }
    }

    public PushProgram(PushProgram parent) {
        this.expressions = new LinkedList<Expression>();
        for (Expression e : parent.expressions) {
            this.expressions.add(e.copy());
        }
        this.fitness = parent.fitness;
    }

    public State evaluate(float... arguments) {
        state = new State(expressions);
        state.typeStack.push(Type.Literal.FLOAT);
        state.typeStack.push(Type.Literal.INTEGER);
        state.typeStack.push(Type.Literal.BOOLEAN);
        state.typeStack.push(Type.Literal.NAME);
        state.typeStack.push(Type.Literal.TYPE);
        state.typeStack.push(Type.Literal.CODE);
        for (int i = 0; i < arguments.length; i++) {
            state.floatStack.push(arguments[i]);
        }
        while (!state.execStack.empty()) {
            Expression expr = (Expression) state.execStack.pop();
            switch (expr.getType()) {
                case INSTRUCTION:
                    Method method = expr.getInstruction();
                    try {
                        method.invoke(null, state);
                    } catch (InvocationTargetException ex) {
                        System.err.println(method.getName());
                        ex.printStackTrace();
                        System.exit(1);
                    } catch (IllegalAccessException ex) {
                        System.err.println(method.getName());
                        ex.printStackTrace();
                        System.exit(1);
                    } catch (Exception ex) {
                        System.err.println(method.getName());
                        ex.printStackTrace();
                        System.exit(1);
                    }
                    break;
                case LITERAL:
                    state.stack(expr.getLiteralType()).push(expr.getLiteral());
                    break;
                case LIST:
                    for (int i = expr.getProgram().size(); i >= 0; i--) {
                        state.execStack.push(expr.getProgram().get(i));
                    }
                    break;
            }
        }
        return state;
    }

    public static List<Method> getInstructions() {
        if (instructions == null) {
            instructions = new ArrayList<Method>();
            List<Class> commandClasses = new LinkedList<Class>();
            commandClasses.add(BaseInstructions.class);
            commandClasses.add(NumberInstructions.class);
            commandClasses.add(BooleanInstructions.class);
            for (Class clazz : commandClasses) {
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (method.getName().equals(method.getName().toUpperCase())) {
                        instructions.add(method);
                    }
                }
            }
        }
        return instructions;
    }
}
